// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include <zencore/iobuffer.h>
#include <zencore/iohash.h>
#include <zencore/logging.h>
#include <zencore/memory.h>
#include <zencore/thread.h>
#include <zencore/uid.h>
#include <zencore/zencore.h>

ZEN_THIRD_PARTY_INCLUDES_START
#include <tsl/robin_map.h>
#include <asio.hpp>
ZEN_THIRD_PARTY_INCLUDES_END

#include <chrono>
#include <list>

struct ZenCacheValue;

namespace zen {

class CbObjectWriter;
class CbObjectView;
class CbPackage;
class ZenStructuredCacheClient;

//////////////////////////////////////////////////////////////////////////

namespace detail {
	struct ZenCacheSessionState;
}

struct ZenCacheResult
{
	IoBuffer	Response;
	int64_t		Bytes		   = {};
	double		ElapsedSeconds = {};
	int32_t		ErrorCode	   = {};
	std::string Reason;
	bool		Success = false;
};

struct ZenStructuredCacheClientOptions
{
	std::string_view			 Name;
	std::string_view			 Url;
	std::span<std::string const> Urls;
	std::chrono::milliseconds	 ConnectTimeout{};
	std::chrono::milliseconds	 Timeout{};
};

/** Zen Structured Cache session
 *
 * This provides a context in which cache queries can be performed
 *
 * These are currently all synchronous. Will need to be made asynchronous
 */
class ZenStructuredCacheSession
{
public:
	ZenStructuredCacheSession(Ref<ZenStructuredCacheClient>&& OuterClient);
	~ZenStructuredCacheSession();

	ZenCacheResult CheckHealth();
	ZenCacheResult GetCacheRecord(std::string_view Namespace, std::string_view BucketId, const IoHash& Key, ZenContentType Type);
	ZenCacheResult GetCacheChunk(std::string_view Namespace, std::string_view BucketId, const IoHash& Key, const IoHash& ValueContentId);
	ZenCacheResult PutCacheRecord(std::string_view Namespace,
								  std::string_view BucketId,
								  const IoHash&	   Key,
								  IoBuffer		   Value,
								  ZenContentType   Type);
	ZenCacheResult PutCacheValue(std::string_view Namespace,
								 std::string_view BucketId,
								 const IoHash&	  Key,
								 const IoHash&	  ValueContentId,
								 IoBuffer		  Payload);
	ZenCacheResult InvokeRpc(const CbObjectView& Request);
	ZenCacheResult InvokeRpc(const CbPackage& Package);

private:
	inline LoggerRef Log() { return m_Log; }

	LoggerRef					  m_Log;
	Ref<ZenStructuredCacheClient> m_Client;
	detail::ZenCacheSessionState* m_SessionState;
};

/** Zen Structured Cache client
 *
 * This represents an endpoint to query -- actual queries should be done via
 * ZenStructuredCacheSession
 */
class ZenStructuredCacheClient : public RefCounted
{
public:
	ZenStructuredCacheClient(const ZenStructuredCacheClientOptions& Options);
	~ZenStructuredCacheClient();

	std::string_view ServiceUrl() const { return m_ServiceUrl; }

	inline LoggerRef Log() { return m_Log; }

private:
	LoggerRef				  m_Log;
	std::string				  m_ServiceUrl;
	std::chrono::milliseconds m_ConnectTimeout;
	std::chrono::milliseconds m_Timeout;

	RwLock									 m_SessionStateLock;
	std::list<detail::ZenCacheSessionState*> m_SessionStateCache;

	detail::ZenCacheSessionState* AllocSessionState();
	void						  FreeSessionState(detail::ZenCacheSessionState*);

	friend class ZenStructuredCacheSession;
};

}  // namespace zen
